package service

import (
	"context"
	"devops/middleware"
	"devops/models"
	"devops/pkg/jenkins/api_type"
	"devops/pkg/tool"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"sync"
	"time"

	"github.com/bndr/gojenkins"
)

func (d *devOps) PiplineRun(projectID int, cid int, user middleware.User) (*models.Build, error) {
	ci := models.GetCI(projectID, cid)
	if ci == nil {
		return nil, errors.New("未找到对应的构建计划")
	}
	project := models.GetProjectByID(projectID)
	if project.ID == 0 {
		return nil, errors.New("项目不存在")
	}
	//lastNumber := models.GetLastBuildNumber(ci.ID)
	//// 记录数据库
	//curNumber := lastNumber + 1
	resp, err := d.RunPipeline(project.Name, ci.Name)
	if err != nil {
		return nil, err
	}
	number, _ := strconv.Atoi(resp.ID)
	queueID, _ := strconv.Atoi(resp.QueueID)
	b := &models.Build{
		CID:              cid,
		Number:           number,
		Result:           resp.Result,
		QueueId:          queueID,
		DisplayName:      fmt.Sprintf("#%d", number),
		FullName:         fmt.Sprintf("%s #%d", ci.Name, number),
		Timestamp:        0,
		Duration:         0,
		Estimateddurati:  0,
		StartUser:        user.UserName,
		ShortDescription: user.UserName + "手动构建",
		RemoteURL:        ci.RepositoryUrl,
		Type:             1,
	}
	// 查看是否需要保存镜像版本号
	b.ImageTag = d.GetImageVersion(*b)
	models.CreateBuild(b)

	// 设置cd
	go d.InsertCD(cid, b.ID)
	return b, nil
}

func (d *devOps) PipelineRunByWebHook(ci *models.CI, startMsg string) {
	project := models.GetProjectByID(ci.ProjectId)
	if project.ID == 0 {
		return
	}
	resp, err := d.RunPipeline(project.Name, ci.Name)
	if err != nil {
		return
	}
	number, _ := strconv.Atoi(resp.ID)
	queueID, _ := strconv.Atoi(resp.QueueID)
	b := &models.Build{
		CID:              ci.ID,
		Number:           number,
		Result:           resp.Result,
		QueueId:          queueID,
		DisplayName:      fmt.Sprintf("#%d", number),
		FullName:         fmt.Sprintf("%s #%d", ci.Name, number),
		Timestamp:        0,
		Duration:         0,
		Estimateddurati:  0,
		StartUser:        startMsg,
		ShortDescription: "[GIT COMMIT] " + startMsg,
		RemoteURL:        ci.RepositoryUrl,
		Type:             3,
	}
	// 查看是否需要保存镜像版本号
	b.ImageTag = d.GetImageVersion(*b)
	models.CreateBuild(b)

	// 设置cd
	go d.InsertCD(ci.ID, b.ID)
	return
}

func (d *devOps) PiplineStop(projectID int, cid int, buildId int) error {
	ci := models.GetCI(projectID, cid)
	if ci == nil {
		return errors.New("未找到对应的构建计划")
	}
	build := models.GetBuildByID(buildId)
	if build == nil {
		return errors.New("未找到 build")
	}
	ctx := context.Background()
	jksBuild, err := d.jks.GetBuild(ctx, ci.Name, int64(build.Number))
	if err != nil {
		return err
	}
	_, err = jksBuild.Stop(ctx)
	return err
}

func (d *devOps) GetPiplineBuild(projectID int, cid int, buildID int) (*models.Build, error) {
	project := models.GetProjectByID(projectID)
	if project.ID == 0 {
		return nil, errors.New("项目不存在")
	}

	ci := models.GetCI(projectID, cid)
	if ci == nil {
		return nil, errors.New("未找到对应的构建计划")
	}

	build := models.GetBuild(buildID, cid)
	if build.Number != buildID {
		return nil, errors.New("未找到对应build")
	}
	if build.Result == "SUCCESS" {
		return build, nil
	}
	d.SyncBuildResult(ci.Name, project.Name, build)
	return build, nil
}

// GetStage 构建计划详情的轮询接口
func (d *devOps) GetStage(projectID int, ciName string, buildID int) (*models.BuildStageInfoResponse, error) {
	build := models.GetBuildByID(buildID)
	if build == nil {
		return nil, errors.New("build 不存在")
	}
	project := models.GetProjectByID(projectID)
	if project.ID != projectID {
		return nil, errors.New("项目不存在")
	}
	// 这里分两种情况
	// 如果 结果是未完成， 则去请求 jenkins api
	//if build.Result == "UNKNOWN" || build.Result == "" {
	group := sync.WaitGroup{}
	if jksStages, err := d.GetPiplineRunStages(ciName, project.Name, build.Number); err == nil {
		for _, s := range jksStages {
			group.Add(1)
			go func(stage api_type.PipelineRunStage) {
				d.SyncBuildStageAndFlow(stage, buildID, ciName, project.Name, build.Number)
				defer group.Done()
			}(s)
		}
	}

	group.Add(1)
	go func() {
		d.SyncBuildResult(ciName, project.Name, build)
		defer group.Done()
	}()

	group.Wait()
	//}

	return &models.BuildStageInfoResponse{
		BuildNumber:      build.Number,
		Result:           build.Result,
		Timestamp:        build.Timestamp,
		Duration:         build.Duration,
		ShortDescription: build.ShortDescription,
		Stages:           models.GetStageByBuildIDAndFlows(buildID),
	}, nil
}

func (d *devOps) GetBuildStageSimple(buildIds []int) []models.BuildStageSimple {
	buildStageSimple := make([]models.BuildStageSimple, 0)
	for _, bid := range buildIds {
		buildStageSimple = append(buildStageSimple, models.BuildStageSimple{
			BuildID: bid,
			Stages:  models.GetBuildStages(bid),
		})
	}
	return buildStageSimple
}

//func (d *devOps) GetBuildStage(buildID int) (*models.BuildOneResponse, error) {
//	build := models.GetBuildByID(buildID)
//	if build == nil {
//		return nil, errors.New("build 不存在")
//	}
//	ci := models.GetCIByID(build.CID)
//	if ci == nil {
//		return nil, errors.New("构建计划 不存在")
//	}
//	project := models.GetProjectByID(ci.ProjectId)
//	if project.ID == 0 {
//		return nil, errors.New("项目不存在")
//	}
//	// 并行处理
//	wait := sync.WaitGroup{}
//	wait.Add(1)
//	go func() {
//		d.SyncBuildResult(ci.Name, project.Name, build)
//		defer wait.Done()
//	}()
//	if jksStages, err := d.GetPiplineRunStages(ci.Name, project.Name, build.Number); err == nil {
//		for _, s := range jksStages {
//			wait.Add(1)
//			go func(node api_type.PipelineRunStage) {
//				d.SyncBuildStageAndFlow(node, buildID, ci.Name, project.Name, build.Number)
//				defer wait.Done()
//			}(s)
//		}
//	}
//	wait.Wait()
//
//	return &models.BuildOneResponse{
//		CID:              ci.ID,
//		CiName:           ci.Name,
//		BuildNumber:      build.Number,
//		Result:           build.Result,
//		ShortDescription: build.ShortDescription,
//		Timestamp:        build.Timestamp,
//		RemoteURL:        build.RemoteURL,
//		Branch:           ci.Branch,
//		Duration:         build.Duration,
//		Stages:           models.GetStageByBuildIDAndFlows(buildID),
//	}, nil
//}

func (d *devOps) GetStageFlows(s *models.BuildStage, ciName string, projectName string, buildNumber int, buildID int) {
	info := d.ExecutionNodeDescribe(ciName, projectName, buildNumber, s.Number)
	for _, flow := range info.StageFlowNodes {
		number, _ := strconv.Atoi(flow.ID)
		stageInfo := models.GetBuildStageInfo(s.ID, number)
		text := ""
		if log := d.ExecutionNodeLog(ciName, projectName, buildNumber, number); log != nil {
			text = log.Text
		}
		if stageInfo.ID == 0 {
			// create
			models.CreateBuildStageInfo(&models.BuildStageInfo{
				BuildID:   buildID,
				StageID:   s.ID,
				Name:      flow.Name,
				Result:    flow.Status,
				Timestamp: flow.StartTimeMillis,
				Duration:  flow.DurationMillis,
				Number:    number,
				Log:       text,
			})
		} else if stageInfo.Result == "" || stageInfo.Result == "UNKNOWN" {
			// update
			models.UpdateBuildStageInfo(stageInfo.ID, models.BuildStageInfo{
				Result:    flow.Status,
				Timestamp: flow.StartTimeMillis,
				Duration:  flow.DurationMillis,
				Log:       text,
			})
		}
	}
}

func (d *devOps) GetAllPiplineBuild(projectID int, cid int, page int, pageSize int) ([]*models.Build, int) {
	ci := models.GetCI(projectID, cid)
	if ci == nil {
		return nil, 0
	}
	project := models.GetProjectByID(projectID)
	if project.ID != projectID {
		return nil, 0
	}

	d.SyncCiBuild(*ci, project.Name)
	//if err == nil {
	//	wait := sync.WaitGroup{}
	//	for _, jksBuild := range jksBuilds {
	//		wait.Add(1)
	//		go func(jksB gojenkins.JobBuild) {
	//			defer wait.Done()
	//			build, err := d.jks.GetBuild(ctx, jksJobName, jksBuild.Number)
	//			if err != nil {
	//				return
	//			}
	//			info := build.Info()
	//			b := models.GetBuild(int(jksBuild.Number), cid)
	//			var startUser, shortDesc string
	//			causes, err := build.GetCauses(ctx)
	//			if err == nil && len(causes) > 0{
	//				if causes[0]["_class"] == "hudson.triggers.TimerTrigger$TimerTriggerCause" {
	//					startUser = "定时器"
	//					shortDesc = "由 定时器 自动构建"
	//				} else {
	//					shortDesc = causes[0]["shortDescription"].(string)
	//				}
	//			}
	//			if b.ID == 0 {
	//				models.CreateBuild(&models.Build{
	//					CID: cid,
	//					Number: int(info.Number),
	//					Result: info.Result,
	//					QueueId: int(info.QueueID),
	//					DisplayName: info.DisplayName,
	//					FullName: info.FullDisplayName,
	//					Timestamp: info.Timestamp,
	//					Duration: int64(info.Duration),
	//					Estimateddurati: int64(info.EstimatedDuration),
	//					StartUser: startUser,
	//					ShortDescription: shortDesc,
	//					RemoteURL: ci.RepositoryUrl,
	//				})
	//			}
	//		}(jksBuild)
	//
	//	}
	//	wait.Wait()
	//}
	builds, total := models.GetAllBuildPage(cid, page, pageSize)
	return builds, total
}

func (d *devOps) GetStageInfoLog(flowId int) *models.BuildStageInfo {
	info := models.GetBuildStageInfoByID(flowId)
	if info.Result != "" && info.Result != "UNKNOWN" && info.Log != "" {
		return &info
	}
	stage := models.GetBuildStageByID(info.StageID)
	if stage.ID == 0 {
		return &info
	}
	build := models.GetBuildByID(stage.BuildId)
	if build == nil {
		return &info
	}
	ci := models.GetCIByID(build.CID)
	if ci == nil {
		return &info
	}
	project := models.GetProjectByID(ci.ProjectId)
	if project.ID == 0 {
		return &info
	}
	log := d.ExecutionNodeLog(ci.Name, project.Name, build.Number, info.Number)
	models.UpdateBuildStageInfo(flowId, models.BuildStageInfo{
		Log:    log.Text,
		Result: log.NodeStatus,
	})
	info.Log = log.Text
	info.Result = log.NodeStatus
	return &info
}

func (d *devOps) DeleteBuild(buildID int) error {
	build := models.GetBuildByID(buildID)
	if build == nil {
		return nil
	}
	ci := models.GetCIByID(build.CID)
	if ci == nil {
		return nil
	}
	project := models.GetProjectByID(ci.ProjectId)
	if project.ID == 0 {
		return nil
	}
	err := d.DeletePipelineBuild(ci.Name, project.Name, build.Number)
	if err != nil {
		return err
	}
	// 删除数据库
	return models.DeleteBuildWithOther(buildID)
}

func (d *devOps) GetTestReport(buildID int) *gojenkins.TestResult {
	build := models.GetBuildByID(buildID)
	if build == nil {
		return nil
	}
	ci := models.GetCIByID(build.CID)
	if ci == nil {
		return nil
	}
	project := models.GetProjectByID(ci.ProjectId)
	if project.ID == 0 {
		return nil
	}

	test, _ := d.TestReport(ci.Name, project.Name, build.Number)
	return test
}

func (d *devOps) GetArtifact(buildID int) []*models.BuildArtifact {
	build := models.GetBuildByID(buildID)
	if build == nil {
		return nil
	}
	ci := models.GetCIByID(build.CID)
	if ci == nil {
		return nil
	}
	project := models.GetProjectByID(ci.ProjectId)
	if project.ID == 0 {
		return nil
	}
	arts := models.GetArtifacts(buildID)
	if len(arts) == 0 {
		// 去 jks 取
		jksArtifacts, _ := d.Artifacts(ci.Name, project.Name, build.Number)
		for _, art := range jksArtifacts {
			arts = append(arts, &models.BuildArtifact{
				BuildId:     buildID,
				Size:        art.Size,
				Name:        art.Name,
				JenkinsPath: art.URL,
			})
		}
		models.BatchCreateArtifacts(arts)
	}
	return arts
}

func (d *devOps) DownloadArtifact(path string) []byte {
	client := &http.Client{}
	request, _ := http.NewRequest("GET", d.jks.Requester.Base+path, nil)
	request.SetBasicAuth(d.jks.Requester.BasicAuth.Username, d.jks.Requester.BasicAuth.Password)
	resp, err := client.Do(request)
	if err != nil {
		return nil
	}
	c, _ := ioutil.ReadAll(resp.Body)
	defer resp.Body.Close()
	return c
}

func (d *devOps) GetConsoleOutput(buildID int) (string, error) {
	build := models.GetBuildByID(buildID)
	if build == nil {
		return "", errors.New("build 不存在")
	}
	ci := models.GetCIByID(build.CID)
	if ci == nil {
		return "", errors.New("构建计划不存在")
	}
	project := models.GetProjectByID(ci.ProjectId)
	if project.ID == 0 {
		return "", errors.New("项目不存在")
	}
	return d.PiplineBuildConsole(ci.Name, project.Name, build.Number)
}

//127.0.0.1:8080/api/pipeline/build/artifacts/download?name=123132.md&path=/job/%E5%AE%89%E7%95%85devops/job/coding/9/artifact/README.md

// StringParameterDefinition 字符串输入
// ChoiceParameterDefinition 下拉列表
func (d *devOps) GetInputAction(buildID int) (*api_type.InputAction, error) {
	build := models.GetBuildByID(buildID)
	if build == nil {
		return nil, errors.New("build 不存在")
	}
	ci := models.GetCIByID(build.CID)
	if ci == nil {
		return nil, errors.New("构建计划不存在")
	}
	project := models.GetProjectByID(ci.ProjectId)
	if project.ID == 0 {
		return nil, errors.New("项目不存在")
	}

	return d.NextPendingInputAction(ci.Name, project.Name, build.Number)
}

func (d *devOps) InputSubmit(buildID int, submit bool, Stage string, Name string, Value string) error {
	build := models.GetBuildByID(buildID)
	if build == nil {
		return errors.New("build 不存在")
	}
	ci := models.GetCIByID(build.CID)
	if ci == nil {
		return errors.New("构建计划不存在")
	}
	project := models.GetProjectByID(ci.ProjectId)
	if project.ID == 0 {
		return errors.New("项目不存在")
	}
	Stage = url.QueryEscape(Stage)
	if submit {
		return d.JenkinsInputSubmit(ci.Name, project.Name, build.Number, Stage, Name, Value)
	}
	return d.JenkinsInputAbort(ci.Name, project.Name, build.Number, Stage)
}

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////

func (d *devOps) SyncBuildResult(jobName string, folder string, build *models.Build) {
	// 每次同步 build 状态完成之后 ， 检查是否需要启动部署组
	// 重启部署组方法判断是否需要重启
	defer d.RestartDeployGroup(build)

	if build == nil || build.Result == "SUCCESS" || build.Result == "FAILURE" {
		return
	}
	jksBuild, err := d.jks.GetBuild(context.Background(), folder+"/job/"+jobName, int64(build.Number))
	if err != nil || jksBuild.GetResult() == "" {
		return
	}

	info := jksBuild.Info()
	models.UpdateBuild(build.ID, models.Build{
		Result:          info.Result,
		Duration:        int64(info.Duration),
		Estimateddurati: int64(info.EstimatedDuration),
		Timestamp:       info.Timestamp,
	})
	var eventType EventType
	if info.Result == "SUCCESS" {
		eventType = BuildSuccess
	} else if info.Result == "FAILURE" {
		eventType = BuildFail
	}

	build.Result = info.Result
	build.Duration = int64(info.Duration)
	build.Estimateddurati = int64(info.EstimatedDuration)
	build.Timestamp = info.Timestamp
	// 记录事件
	go d.BuildEvent(eventType, nil, *build)
}

func (d *devOps) SyncCiBuild(ci models.CI, projectName string) {
	jksBuildIDs, _ := d.GetBuildIDs(ci.Name, projectName)
	builds := models.GetAllBuildNumber(ci.ID)
	// 获取没有记录到的构建记录
	needCreate := tool.IntSliceDiff(jksBuildIDs, builds)
	//needDelete := tool.IntSliceDiff(builds, jksBuildIDs)
	needUpdate := tool.IntSliceIntersect(builds, jksBuildIDs)
	// 补记录
	if len(needCreate) > 0 {
		//createData := make([]models.Build, 0, len(needCreate))
		for _, not := range needCreate {
			ctx := context.Background()
			build, err := d.jks.GetBuild(ctx, projectName+"/job/"+ci.Name, int64(not))
			if err != nil {
				continue
			}
			var startUser, shortDesc string
			typ := 1
			causes, err := build.GetCauses(ctx)
			if err != nil && len(causes) > 0 {
				if causes[0]["_class"] == "hudson.triggers.TimerTrigger$TimerTriggerCause" {
					startUser = "定时器"
					shortDesc = "由 定时器 自动构建"
					typ = 2
				} else {
					shortDesc = causes[0]["shortDescription"].(string)
				}
			}
			info := build.Info()
			b := models.Build{
				CID:              ci.ID,
				Number:           int(info.Number),
				Result:           info.Result,
				QueueId:          int(info.QueueID),
				DisplayName:      info.DisplayName,
				FullName:         info.FullDisplayName,
				Timestamp:        info.Timestamp,
				Duration:         int64(info.Duration),
				Estimateddurati:  int64(info.EstimatedDuration),
				StartUser:        startUser,
				ShortDescription: shortDesc,
				RemoteURL:        ci.RepositoryUrl,
				Type:             typ,
			}
			// 查看是否需要保存镜像版本号
			b.ImageTag = d.GetImageVersion(b)
			models.CreateBuild(&b)
			if b.StartUser == "定时器" {
				go d.BuildEvent(BuildTimerRun, nil, b)
			}
			// 补上漏的定时器则判断是否需要重启部署组
			d.RestartDeployGroup(&b)
			//// 构造批量插入数据
			//createData = append(createData, models.Build{
			//	CID: ci.ID,
			//	Number: int(info.Number),
			//	Result: info.Result,
			//	QueueId: int(info.QueueID),
			//	DisplayName: info.DisplayName,
			//	FullName: info.FullDisplayName,
			//	Timestamp: info.Timestamp,
			//	Duration: int64(info.Duration),
			//	Estimateddurati: int64(info.EstimatedDuration),
			//	StartUser: startUser,
			//	ShortDescription: shortDesc,
			//	RemoteURL: ci.RepositoryUrl,
			//})
		}
		//models.CreateBuilds(createData)
	}

	// 删脏数据
	//if len(needDelete) > 0 {
	//	for _, id := range needDelete {
	//		b := models.GetBuild(id, ci.ID)
	//		models.DeleteBuildWithOther(b.ID)
	//	}
	//}

	// 更改状态
	if len(needUpdate) > 0 {
		for _, id := range needUpdate {
			b := models.GetBuild(id, ci.ID)
			if b != nil {
				d.SyncBuildResult(ci.Name, projectName, b)
			}
		}
	}
}

func (d *devOps) SyncBuildStageAndFlow(jksStage api_type.PipelineRunStage, buildID int, ciName string, projectName string, buildNumber int) {
	stageNumber, _ := strconv.Atoi(jksStage.ID)
	parentNumber, err := strconv.Atoi(jksStage.FirstParent)
	if err != nil {
		parentNumber = 0
	}
	bs := models.GetBuildStage(buildID, stageNumber)
	startTime, _ := time.Parse("2006-01-02T15:04:05.000+0000", jksStage.StartTime)
	if bs.ID == 0 {
		//不存在则直接创建
		bs = models.BuildStage{
			BuildId:     buildID,
			Number:      stageNumber,
			Name:        jksStage.DisplayName,
			Result:      jksStage.Result,
			Timestamp:   startTime.UnixNano() / 1e6,
			Duration:    int64(jksStage.DurationInMillis),
			Type:        jksStage.Type,
			FirstParent: parentNumber,
			State:       jksStage.State,
		}
		models.CreateBuildStage(&bs)
	} else {
		// 存在则更新
		models.UpdateBuildStage(bs.ID, models.BuildStage{
			Result:      jksStage.Result,
			Timestamp:   startTime.UnixNano() / 1e6,
			Duration:    int64(jksStage.DurationInMillis),
			Type:        jksStage.Type,
			FirstParent: parentNumber,
			State:       jksStage.State,
		})
	}
	d.GetStageFlows(&bs, ciName, projectName, buildNumber, buildID)
}
